#include <iostream>
#include <cmath>
#include <memory>
#include <GL/glut.h>    // Header File For The GLUT Library 
#include <GL/gl.h>	// Header File For The OpenGL32 Library
#include <GL/glu.h>	// Header File For The GLu32 Library
#include <unistd.h>     // Header File For sleeping.
#include <stdio.h>
#include <string.h>
#include "../libfractal/mandelbrot.hpp"
#include "../libfractal/newton.hpp"
#include "../libfractal/julia.hpp"
#include "../libfractal/fvector.hpp"


using namespace std;


/* ASCII code for the escape key. */
#define ESCAPE 27


#define WIDTH 512
#define HEIGHT 512
#define MAX_ITER 64
#define HALF_WIDTH 2.0
#define HALF_HEIGHT 2.0


/* The number of our GLUT window */
int window;


int cur_wnd_width, cur_wnd_height;
GLdouble zoom_factor = 1.0;
ColormapLevel cm(MAX_ITER);
complex<double> c(0.4, 0.2);
Mandelbrot m(WIDTH, HEIGHT, MAX_ITER, -HALF_WIDTH, HALF_WIDTH, -HALF_HEIGHT, HALF_HEIGHT);
FVector2d cur_pos;
FBitmap bm;
float redmap[MAX_ITER], greenmap[MAX_ITER], bluemap[MAX_ITER];


void render(void)
{
    m.left = cur_pos.x - (double)HALF_WIDTH / zoom_factor;
    m.right = cur_pos.x + (double)HALF_WIDTH / zoom_factor;
    m.bottom = cur_pos.y - (double)HALF_HEIGHT / zoom_factor;
    m.top = cur_pos.y + (double)HALF_HEIGHT / zoom_factor;
    m.render(bm, cm);
    glutPostWindowRedisplay(window);
}


void reset_state(void)
{
    zoom_factor = 1.0;
    cur_pos = FVector2d(0.0, 0.0);

    m.width = WIDTH;
    m.height = HEIGHT;
    m.max_iter = MAX_ITER;

    render();
}


void set_projection_matrix()
{
    float rate;

    if (cur_wnd_height == 0)
	cur_wnd_height = 1;
    if (cur_wnd_width == 0)
	cur_wnd_width = 1;

    if (cur_wnd_width < cur_wnd_height)
	rate = (float)cur_wnd_height / cur_wnd_width;
    else
	rate = (float)cur_wnd_width / cur_wnd_height;

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    if (cur_wnd_width < cur_wnd_height)
	gluOrtho2D(0.0f, 4.0f, 
		   0.0f, 4.0f);
    else
	gluOrtho2D(0.0f, 4.0f,
		   0.0f, 4.0f);

    glMatrixMode(GL_MODELVIEW);
}


void init_gl(int w, int h)
{
    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
    glShadeModel(GL_SMOOTH);

    cur_wnd_width = w; cur_wnd_height = h;
    set_projection_matrix();
    reset_state();
}


void resize_gl_scene(int w, int h)
{
    glViewport(0, 0, w, h);
    cur_wnd_width = w; cur_wnd_height = h;
    set_projection_matrix();
}


/* The main drawing function. */
void draw_gl_scene()
{
    // Clear The Screen And The Depth Buffer
    glClear(GL_COLOR_BUFFER_BIT);
    glLoadIdentity();  // Reset The View
    glDrawPixels(WIDTH, HEIGHT, GL_RGB, GL_FLOAT, bm.c_ptr());
    // swap buffers to display, since we're double buffered.
    glutSwapBuffers();
}


/* The function called whenever a key is pressed. */
void key_pressed(unsigned char key, int x, int y) 
{
//    int i;
    /* avoid thrashing this procedure */
    usleep(100);

    /* If escape is pressed, kill everything. */
    switch (key) 
    { 
    case ESCAPE:
        /* shut down our window */
    	glutDestroyWindow(window);
    	/* vertice the program...normal termination. */
    	exit(0);
    	break;
    case 'w':
	cur_pos.y += 0.1;
	render();
    	break;
    case 's':
	cur_pos.y -= 0.1;
	render();
    	break;
    case 'a':
	cur_pos.x -= 0.1;
	render();
	break;
    case 'd':
	cur_pos.x += 0.1;
	render();
	break;
    case 't':
	zoom_factor += 1.0;
	render();
	break;
    case 'g':
	if (zoom_factor > 1.0) {
	    zoom_factor -= 1.0;
	    render();
	}
	break;
    case 'r':
	reset_state();
    	break;
    }
}


int main(int argc, char **argv) 
{  
    glutInit(&argc, argv);  
    glutInitDisplayMode(GLUT_RGB);  

    glutInitWindowSize(WIDTH, HEIGHT);  
    glutInitWindowPosition(0, 0);  
    window = glutCreateWindow("mandelbrot");  

    glutDisplayFunc(&draw_gl_scene);  
//    glutFullScreen();

//    glutIdleFunc(&draw_gl_scene);
    glutReshapeFunc(&resize_gl_scene);
    glutKeyboardFunc(&key_pressed);

    init_gl(WIDTH, HEIGHT);
  
    glutMainLoop();  

    return 1;
}
